﻿using System;
using PoolKit.Commands;
using PoolKit.Extensions;

namespace PoolKit
{
    internal class RepairAndPrepareForReuseCommand<TResource> : ResourceCommand<TResource>
        where TResource : class
    {
        private readonly ReusePreparationStrategy<TResource> _reusePreparationStrategy;
        private readonly IResourceCollection<TResource> _resourceCollection;
        private readonly RepairStrategy<TResource> _repairStrategy;
        private readonly PrepareForReuseCommand<TResource> _prepareForReuseCommand;

        public RepairAndPrepareForReuseCommand(
            ReusePreparationStrategy<TResource> reusePreparationStrategy,
            IResourceCollection<TResource> resourceCollection,
            TResource resource,
            RepairStrategy<TResource> repairStrategy) : base(resource)
        {
            if (reusePreparationStrategy == null) throw new ArgumentNullException("reusePreparationStrategy");
            if (resourceCollection == null) throw new ArgumentNullException("resourceCollection");
            if (repairStrategy == null) throw new ArgumentNullException("repairStrategy");

            _reusePreparationStrategy = reusePreparationStrategy;
            _repairStrategy = repairStrategy;
            _resourceCollection = resourceCollection;
            _prepareForReuseCommand = new PrepareForReuseCommand<TResource>(_reusePreparationStrategy, _resourceCollection, Resource);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <exception cref="FailedToRepairResourceException"></exception>
        /// <exception cref="FailedToPrepareResourceForReuseException"></exception>
        /// <exception cref="ReplacingResourceProducesDuplicateException"></exception>
        public override void Execute()
        {
            try
            {
                TResource resourceRepaired = _repairStrategy.Repair(Resource);

                // they've given us a replacement - fine
                if (!ReferenceEquals(resourceRepaired, Resource))
                {
                    _resourceCollection.AcceptSubstitution(Resource, resourceRepaired);
                }
                else
                {
                    _prepareForReuseCommand.Execute();
                }
            }
            catch (ReplacingResourceProducesDuplicateException exception)
            {
                throw new ReplacingResourceProducesDuplicateException(
                    "{0}. {1}"
                    .FormatWith(
                        exception.Message,
                        Resources.ReplacingProducingDuplicatesCheckYourStrategy
                        .FormatWith(_repairStrategy.ToString())));
            }
            catch (FailedToPrepareResourceForReuseException)
            {
                throw;
            }
            catch (Exception exception)
            {
                if (exception.IsNotWrappable())
                {
                    throw;
                }

                throw new FailedToRepairResourceException(
                    Resources.RepairingResourceFailed
                    .FormatWith(Environment.NewLine, _repairStrategy.ToString(), Resource.ToString()),
                    exception);
            }
        }
    }
}